/*********************************************************************************
 *      Copyright:  (C) 2018 LingYun IoT System Studio
 *                  All rights reserved.
 *
 *       Filename:  logger.c
 *    Description:  This file is the linux infrastructural logger system library, which
 *                  is not thread safe.
 *                 
 *        Version:  1.0.0(08/08/2012~)
 *         Author:  Guo Wenxue <guowenxue@gmail.com>
 *      ChangeLog:  1, Release initial version on "08/08/2018 04:24:01 PM"
 *                 
 ********************************************************************************/
 
#include "logger.h"
 
#define PRECISE_TIME_FACTOR 1000
 
static unsigned long log_rollback_size = LOG_ROLLBACK_NONE;
 
static st_logger _g_logger;
static st_logger *g_logger = &_g_logger;
 
char *log_str[LOG_LEVEL_MAX + 1] = { "", "F", "E", "W", "N", "D", "I", "T", "M" };
 
static char *log_time_format = "%Y-%m-%d %H:%M:%S";
 
static void logger_default_signal_handler(int sig)
{
    if (sig == SIGHUP)
    {
        signal(SIGHUP, logger_default_signal_handler);
        log_fatal("SIGHUP received - reopenning log file [%s]", g_logger->file);
        logger_reopen();
    }
}
 
static void logger_banner(char *prefix)
{
    fprintf(g_logger->fp, "%s log \"%s\" on level [%s] size [%lu], log system version %s\n",
            prefix, g_logger->file, log_str[g_logger->level], log_rollback_size / 1024, LOG_VERSION_STR);
#if 0
#ifdef LOG_FILE_LINE
    fprintf(g_logger->fp, " [Date]    [Time]   [Level] [PID/TID] [File/Line]  [Content]\n");
#else
    fprintf(g_logger->fp, " [Date]    [Time]   [Level] [PID/TID] [Content]\n");
#endif
#endif
    fprintf(g_logger->fp, "-----------------------------------------------------------------------------\n");
}
 
static void check_and_rollback(void)
{
    if (log_rollback_size != LOG_ROLLBACK_NONE)
    {
        long _curOffset = ftell(g_logger->fp);
 
        if ((_curOffset != -1) && (_curOffset >= log_rollback_size))
        {
            char cmd[512];
 
            snprintf(cmd, sizeof(cmd), "cp -f %s %s.roll", g_logger->file, g_logger->file);
            system(cmd);
 
            if (-1 == fseek(g_logger->fp, 0L, SEEK_SET))
                fprintf(g_logger->fp, "log rollback fseek failed \n");
 
            rewind(g_logger->fp);
 
            truncate(g_logger->file, 0);
            logger_banner("Already rollback");
        }
    }
}
 
int logger_init(char *filename, int level, int log_size)
{
    struct sigaction act;
 
    if( !filename || strlen(filename)<=0 )
    {
        fprintf(stderr, "%s() invalid input arguments\n", __FUNCTION__);
        return -1;
    }
 
    memset(g_logger, 0, sizeof(st_logger));
 
    strncpy(g_logger->file, filename, FILENAME_LEN); 
    g_logger->level = level;
    g_logger->size = log_size; 
 
    log_rollback_size = g_logger->size <= 0 ? LOG_ROLLBACK_NONE : g_logger->size*1024;    /* Unit KiB */
 
    
    /* logger to console, just need point to standard error */
    if (!strcmp(g_logger->file, DBG_LOG_FILE))
    {
        g_logger->fp = stderr;
        log_rollback_size = LOG_ROLLBACK_NONE;
        g_logger->flag |= LOGGER_CONSOLE;
        goto OUT;
    }
 
    g_logger->fp = fopen(g_logger->file, "a+");
    if ( !g_logger->fp )
    {
        fprintf(stderr, "Open log file \"%s\" failure\n", g_logger->file);
        return -2;
    }
 
OUT:
    act.sa_handler = logger_default_signal_handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    sigaction(SIGHUP, &act, NULL);
 
    logger_banner("\nInitialize");
 
    return 0;
}
 
void logger_raw(const char *fmt, ...)
{
    va_list argp;
 
    if ( !g_logger->fp )
        return;
 
    check_and_rollback();
 
    va_start(argp, fmt);
    vfprintf(g_logger->fp, fmt, argp);
    va_end(argp);
}
 
 
void logger_term(void)
{
    if ( !g_logger->fp )
        return;
 
    logger_banner("\nTerminate");
    logger_raw("\n\n");
 
    fflush(g_logger->fp);
 
    fclose(g_logger->fp);
    g_logger->fp = NULL;
 
    memset(g_logger, 0, sizeof(*g_logger));
 
    return ;
}
 
 
int logger_reopen(void)
{
    int rc = 0;
 
    if (g_logger->flag & LOGGER_CONSOLE )
    {
        fflush(g_logger->fp);
        g_logger->fp = stderr;
        return 0;
    }
 
    if( g_logger->fp )
    {
        logger_banner("\nClose");
        fflush(g_logger->fp);
 
        g_logger->fp = fopen(g_logger->file, "a+"); 
        
        if ( !g_logger->fp )
            rc = -2;
    }
    else
    {
        rc = -3;
    }
 
    if (!rc)
    {
        logger_banner("\nReopen");
    }
    return rc;
}
 
static void logger_printout(char *level, char *fmt, va_list argp)
{
    char buf[MAX_LOG_MESSAGE_LEN];
    struct tm *local;
    struct timeval now;
    char timestr[256];
 
    pthread_t tid;
 
    check_and_rollback();
 
#ifdef MULTHREADS
    tid = pthread_self();
#else 
    tid = getpid();
#endif
 
    gettimeofday(&now, NULL);
    local = localtime(&now.tv_sec);
 
    strftime(timestr, 256, log_time_format, local);
    vsnprintf(buf, MAX_LOG_MESSAGE_LEN, fmt, argp);
 
#ifdef DUMPLICATE_OUTPUT
    printf("%s.%03ld [%s] [%06lu]: %s",
           timestr, now.tv_usec / PRECISE_TIME_FACTOR, level, tid, buf);
#endif
 
    if (g_logger->fp)
        fprintf(g_logger->fp, "%s.%03ld [%s] [%06lu]: %s", timestr, now.tv_usec / PRECISE_TIME_FACTOR,
                level, tid, buf);
 
    if (g_logger->fp)
        fflush(g_logger->fp);
}
 
static void logger_printout_line(char *level, char *fmt, char *file, int line, va_list argp)
{
    char buf[MAX_LOG_MESSAGE_LEN];
    struct tm *local;
    struct timeval now;
    char timestr[256];
 
    pthread_t tid;
 
    check_and_rollback();
 
#ifdef MULTHREADS
    tid = pthread_self();
#else 
    tid = getpid();
#endif
 
    gettimeofday(&now, NULL);
    local = localtime(&now.tv_sec);
 
    strftime(timestr, 256, log_time_format, local);
    vsnprintf(buf, MAX_LOG_MESSAGE_LEN, fmt, argp);
 
#ifdef DUMPLICATE_OUTPUT
    printf("%s.%03ld [%s] [%06lu] (%s [%04d]) : %s",
           timestr, now.tv_usec / PRECISE_TIME_FACTOR, level, tid, file, line, buf);
#endif
 
    if (g_logger->fp)
        fprintf(g_logger->fp, "%s.%03ld [%s] [%06lu] (%s [%04d]) : %s",
                timestr, now.tv_usec / PRECISE_TIME_FACTOR, level, tid, file, line, buf);
 
    if (g_logger->fp)
        fflush(g_logger->fp);
}
 
void logger_comm(int level, char *fmt, ...)
{
    va_list argp;
 
    if ( level > g_logger->level )
        return;
 
    va_start(argp, fmt);
    logger_printout(log_str[level], fmt, argp);
    va_end(argp);
}
 
void logger_line(int level, char *file, int line, char *fmt, ...)
{
    va_list argp;
 
    if ( level > g_logger->level )
        return;
 
    va_start(argp, fmt);
    logger_printout_line(log_str[level], fmt, file, line, argp);
 
    va_end(argp);
}
 
#define LINELEN 81
#define CHARS_PER_LINE 16
static char *print_char =
    "                "
    "                "
    " !\"#$%&'()*+,-./"
    "0123456789:;<=>?"
    "@ABCDEFGHIJKLMNO"
    "PQRSTUVWXYZ[\\]^_"
    "`abcdefghijklmno"
    "pqrstuvwxyz{|}~ "
    "                "
    "                "
    " ???????????????"
    "????????????????" 
    "????????????????" 
    "????????????????" 
    "????????????????" 
    "????????????????";
 
void logger_dump(int level, char *buf, int len)
{
    int rc;
    int idx;
    char prn[LINELEN];
    char lit[CHARS_PER_LINE + 2];
    char hc[4];
    short line_done = 1;
 
    if ( level > g_logger->level )
        return;
 
    rc = len;
    idx = 0;
    lit[CHARS_PER_LINE] = '\0';
 
    while (rc > 0)
    {
        if (line_done)
            snprintf(prn, LINELEN, "%08X: ", idx);
 
        do
        {
            unsigned char c = buf[idx];
            snprintf(hc, 4, "%02X ", c);
            strncat(prn, hc, LINELEN);
 
            lit[idx % CHARS_PER_LINE] = print_char[c];
        }
        while (--rc > 0 && (++idx % CHARS_PER_LINE != 0));
 
        line_done = (idx % CHARS_PER_LINE) == 0;
        if (line_done)
        {
#ifdef DUMPLICATE_OUTPUT
            printf("%s  %s\n", prn, lit);
#endif
            if (g_logger->fp)
                fprintf(g_logger->fp, "%s  %s\n", prn, lit);
        }
    }
 
    if (!line_done)
    {
        int ldx = idx % CHARS_PER_LINE;
        lit[ldx++] = print_char[(int)buf[idx]];
        lit[ldx] = '\0';
 
        while ((++idx % CHARS_PER_LINE) != 0)
            strncat(prn, "   ", LINELEN);
 
#ifdef DUMPLICATE_OUTPUT
        printf("%s  %s\n", prn, lit);
#endif
        if (g_logger->fp)
            fprintf(g_logger->fp, "%s  %s\n", prn, lit);
 
    }
}
